using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using System.Net.Sockets;
using System.Diagnostics;
using System.Threading;
using System.Net;
using Coriander.Xmpp;
using Coriander.Xmpp.Jabber;
using Test.Sockets;

namespace Test
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>
    /// [i] The first read from socket succeeds, subsequent ones return empty. 
    ///     For example, ReadHttpAlsoOnlySucceedsOnce shows that
    /// </remarks>
    [TestFixture]
    public class SocketTestFixture
    {
        #region * Fields, properties and ctors *

        Socket socket;
        readonly Uri googleTalk = new Uri("http://talk.google.com:5222");
        IConnectionPolicy connectionPolicy = new JabberConnectionPolicy();
        String start;
        String tls = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";

        /// <summary>
        /// 
        /// </summary>
        public String StartTls
        {
            get { return tls; }
        }

        /// <summary>
        /// Start request
        /// </summary>
        public String Start
        {
            get { 
                return start ?? 
                    (start = connectionPolicy.GetStreamStartHeader("gmail.com"));
             }
        }

        /// <summary>
        /// 
        /// </summary>
        public Socket Socket
        {
            get 
            {
                if (null == socket)
                {
                    socket = SocketFactory.Create();
                    socket.Connect(googleTalk.Host, googleTalk.Port);
                    socket.Blocking = true; 
                }

                return socket;
            }
        }

        #endregion

        [TearDown]
        public void TestTearDown() 
        {
            if (socket != null)
            {
                socket.Close();
                socket = null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        Int32 Send(String message)
        {
            Byte[] msg = Encoding.ASCII.GetBytes(message + "\0");

            Int32 sent = Socket.Send(msg);

            if (sent != msg.Length)
                throw new InvalidOperationException(); 

            Socket.Send(new Byte[0]);

            return sent;
        }

        /// <summary>
        /// Sends and then waits for response.
        /// </summary>
        /// <param name="message"></param>
        /// <returns>The server response.</returns>
        String SendAndReceive(String message)
        {
            Int32 bytesSent = Socket.Send(
                Encoding.ASCII.GetBytes(message)
            );

            Log("SENT: {0}", message);

            String result = SocketUtil.GetReturnedMessage(socket);

            return result;
        }

        /// <summary>
        /// We can do a send, and then a receive. Both succeed.
        /// </summary>
        [Test]
        public void SingleSendReceiveSucceeds() 
        {
            Log("SENT {0} BYTES.", Send(Start));

            Log("READING...");
            Log("RECEIVED: {0}", SocketUtil.GetReturnedMessage(Socket));
        }

        /// <summary>
        /// Doing a blocking send followed by a blocking read each time
        /// seems to succeed.
        /// </summary>
        [Test]
        public void PairsOfSendAndReceivesSucceed() 
        {
            Int32 count = 5;

            for (Int32 i = 0; i < count; i++)
            {
                Log("RECV: {0}", SendAndReceive(Start));
            }
        }

        /// <summary>
        /// If we do everything in a send/receive pairthen we have no trouble.
        /// </summary>
        [Test]
        public void SendingDifferentMessagesUsingPairsOfSendAndReceivesSucceeds()
        {
            Log("RECV: {0}", SendAndReceive(Start));
            Log("RECV: {0}", SendAndReceive(StartTls));
            Log("RECV: {0}", SendAndReceive(StartTls));
            Log("RECV: {0}", SendAndReceive(StartTls));

            // [!] SHOULD respond with a proceed, but it's always 
            //     empty after the first read.
        }

        /// <summary>
        /// We can send as many times as we like, always succeeds.
        /// </summary>
        [Test]
        public void MultipleSendsSucceed() 
        {
            String start = connectionPolicy.
                GetStreamStartHeader("gmail.com");

            Int32 count = 5;

            for (Int32 i = 0; i < count; i++)
            {
                Log("SENT {0} BYTES.", Send(start));
            }
        }

        /// <summary>
        /// If we do not invoke Socket.Send, we can read as much as we like from the 
        /// socket, it returns zero bytes all the time too but does not 
        /// throw exception.
        /// </summary>
        [Test]
        public void MultipleReadsWithoutAWriteSucceed()
        {
            Socket.Connect(googleTalk.Host, googleTalk.Port);
            Socket.Blocking = true;

            Int32 count = 10;

            for (Int32 i = 0; i < count; i++)
            {
                Log("READING...");  
                Log("RECEIVED: \"{0}\"", SocketUtil.GetReturnedMessage(Socket));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        [Test]
        public void MockServerTest() 
        {
            String request = "GET HTTP/1.1\r\n\r\n<EOF>";

            IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
            IPAddress ipAddress = ipHostInfo.AddressList[0];

            using (Socket socket = SocketFactory.Create())
            {
                socket.Connect(new IPEndPoint(ipAddress, 11000));

                socket.Send(Encoding.ASCII.GetBytes(request));

                String response;

                socket.Send(Encoding.ASCII.GetBytes(request));
                response = SocketUtil.GetReturnedMessage(socket);

                Assert.Greater(response.Length, 0);

                Log("RECV: {0} CHARS.", response.Length);

                socket.Send(Encoding.ASCII.GetBytes(request));
                response = SocketUtil.GetReturnedMessage(socket);
                Assert.AreEqual(0, response.Length);

                Log("Second RECV: {0} CHARS.", response.Length);
            }
        }

        /// <summary>
        /// If we send a bunch of statements without reading and then read at the end, 
        /// we do actually get the expected result.
        /// </summary>
        [Test]
        public void MultipleSendWithReceiveAtEndSucceeds() 
        {
            Send(Start);
            Send(StartTls);
            Console.WriteLine(SendAndReceive(""));
            Console.WriteLine(SendAndReceive(""));
        }

        /// <summary>
        /// Subsequent reads on a Socket opened on URL still return nothing.
        /// Only the FIRST READ returns any results.
        /// </summary>
        [Test]
        public void ReadHttpAlsoOnlySucceedsOnce()
        {
            String request = "GET HTTP/1.1\r\n\r\n";

            using (Socket socket = SocketFactory.Create())
            {
                socket.Connect("google.com", 80);
                
                socket.Send(Encoding.ASCII.GetBytes(request));

                String response;

                socket.Send(Encoding.ASCII.GetBytes(request));
                response = SocketUtil.GetReturnedMessage(socket);
                
                Assert.Greater(response.Length, 0);

                Log("RECV: {0} CHARS.", response.Length);

                socket.Send(Encoding.ASCII.GetBytes(request));
                response = SocketUtil.GetReturnedMessage(socket);
                Assert.AreEqual(0, response.Length);

                Log("Second RECV: {0} CHARS.", response.Length);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        static String FormatMessage(String message) 
        {
            return String.Format(
                "[{0}] {1}", 
                DateTime.Now.TimeOfDay, 
                message
            ); 
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <param name="args"></param>
        static void Log(String message, params Object[] args)
        {
            String result = (args.Length > 0) ?
                String.Format(message, args) : message;

            Console.WriteLine(FormatMessage(result));
        }
    }
}
